Kuasai iterasi asinkron di JavaScript menggunakan loop 'for await...of' dan pembantu iterator asinkron kustom. Tingkatkan pemrosesan stream dan penanganan data dengan contoh praktis.
Pembantu Iterator Asinkron JavaScript: For Each - Iterasi Pemrosesan Stream
Pemrograman asinkron adalah landasan pengembangan JavaScript modern, memungkinkan aplikasi untuk menangani operasi yang memakan waktu tanpa memblokir thread utama. Iterator asinkron, yang diperkenalkan dalam ECMAScript 2018, menyediakan mekanisme yang kuat untuk memproses aliran data secara asinkron. Postingan blog ini menyelami konsep iterator asinkron dan mendemonstrasikan cara mengimplementasikan fungsi pembantu 'for each' asinkron untuk menyederhanakan pemrosesan stream.
Memahami Iterator Asinkron
Iterator asinkron adalah objek yang sesuai dengan antarmuka AsyncIterator. Ini mendefinisikan metode next() yang mengembalikan promise, yang akan resolve ke objek dengan dua properti:
value: Nilai berikutnya dalam urutan.done: Boolean yang menunjukkan apakah iterator telah selesai.
Iterator asinkron umumnya digunakan untuk mengonsumsi data dari sumber asinkron seperti stream jaringan, sistem file, atau database. Loop for await...of menyediakan sintaks yang mudah untuk melakukan iterasi pada iterable asinkron.
Contoh: Membaca dari File Secara Asinkron
Bayangkan skenario di mana Anda perlu membaca file besar baris per baris tanpa memblokir thread utama. Anda dapat mencapai ini menggunakan iterator asinkron:
const fs = require('fs');
const readline = require('readline');
async function* readFileLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processFile(filePath) {
for await (const line of readFileLines(filePath)) {
console.log(`Baris: ${line}`);
}
}
// Contoh penggunaan
processFile('path/to/your/file.txt');
Dalam contoh ini, readFileLines adalah fungsi generator asinkron yang menghasilkan setiap baris file saat dibaca. Fungsi processFile kemudian melakukan iterasi pada baris-baris tersebut menggunakan for await...of, memproses setiap baris secara asinkron.
Mengimplementasikan Pembantu 'For Each' Asinkron
Meskipun loop for await...of berguna, ini bisa menjadi bertele-tele ketika Anda perlu melakukan operasi kompleks pada setiap elemen dalam stream. Fungsi pembantu 'for each' asinkron dapat menyederhanakan proses ini dengan merangkum logika iterasi.
Implementasi Dasar
Berikut adalah implementasi dasar dari fungsi 'for each' asinkron:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
await callback(item);
}
}
Fungsi ini menerima iterable asinkron dan fungsi callback sebagai argumen. Ini melakukan iterasi pada iterable menggunakan for await...of dan memanggil fungsi callback untuk setiap item. Fungsi callback juga harus asinkron jika Anda ingin menunggu penyelesaiannya sebelum beralih ke item berikutnya.
Contoh: Memproses Data dari API
Misalkan Anda mengambil data dari API yang mengembalikan aliran item. Anda dapat menggunakan pembantu 'for each' asinkron untuk memproses setiap item saat tiba:
async function* fetchDataStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
return;
}
// Asumsikan API mengembalikan potongan JSON
const chunk = decoder.decode(value);
const items = JSON.parse(`[${chunk.replace(/\}\{/g, '},{')}]`); //Memecah chunk menjadi array json yang valid
for(const item of items){
yield item;
}
}
} finally {
reader.releaseLock();
}
}
async function processItem(item) {
// Mensimulasikan operasi asinkron
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Memproses item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Ganti dengan endpoint API Anda
await asyncForEach(fetchDataStream(apiUrl), processItem);
console.log('Selesai memproses data.');
}
// Contoh penggunaan
main();
Dalam contoh ini, fetchDataStream mengambil data dari API dan menghasilkan setiap item saat diterima. Fungsi processItem mensimulasikan operasi asinkron pada setiap item. Pembantu asyncForEach kemudian menyederhanakan logika iterasi dan pemrosesan.
Peningkatan dan Pertimbangan
Penanganan Kesalahan
Sangat penting untuk menangani kesalahan yang mungkin terjadi selama iterasi asinkron. Anda dapat membungkus fungsi callback dalam blok try...catch untuk menangkap dan menangani pengecualian:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
try {
await callback(item);
} catch (error) {
console.error(`Kesalahan memproses item: ${item}`, error);
// Anda dapat memilih untuk melempar ulang kesalahan atau melanjutkan pemrosesan
}
}
}
Kontrol Konkurensi
Secara default, pembantu 'for each' asinkron memproses item secara berurutan. Jika Anda perlu memproses item secara bersamaan, Anda dapat menggunakan pool Promise untuk membatasi jumlah operasi bersamaan:
async function asyncForEachConcurrent(iterable, callback, concurrency) {
const executing = [];
for await (const item of iterable) {
const p = callback(item).then(() => executing.splice(executing.indexOf(p), 1));
executing.push(p);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
}
async function processItem(item) {
// Mensimulasikan operasi asinkron
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Memproses item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Ganti dengan endpoint API Anda
await asyncForEachConcurrent(fetchDataStream(apiUrl), processItem, 5); // Konkurensi 5
console.log('Selesai memproses data.');
}
Dalam contoh ini, asyncForEachConcurrent membatasi jumlah eksekusi callback bersamaan ke tingkat konkurensi yang ditentukan. Ini dapat meningkatkan kinerja saat menangani aliran data yang besar.
Pembatalan
Dalam beberapa kasus, Anda mungkin perlu membatalkan proses iterasi sebelum waktunya. Anda dapat mencapai ini dengan menggunakan AbortController:
async function asyncForEach(iterable, callback, signal) {
for await (const item of iterable) {
if (signal && signal.aborted) {
console.log('Iterasi dibatalkan.');
return;
}
await callback(item);
}
}
async function main() {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => {
controller.abort(); // Batalkan setelah 2 detik
}, 2000);
const apiUrl = 'https://api.example.com/data'; // Ganti dengan endpoint API Anda
await asyncForEach(fetchDataStream(apiUrl), processItem, signal);
console.log('Selesai memproses data.');
}
Dalam contoh ini, fungsi asyncForEach memeriksa properti signal.aborted sebelum setiap iterasi. Jika sinyal dibatalkan, iterasi dihentikan.
Aplikasi Dunia Nyata
Iterator asinkron dan pembantu 'for each' asinkron dapat diterapkan ke berbagai skenario dunia nyata:
- Pipeline pemrosesan data: Memproses kumpulan data besar dari database atau sistem file.
- Aliran data real-time: Menangani data dari web socket, antrian pesan, atau jaringan sensor.
- Konsumsi API: Mengambil dan memproses data dari API yang mengembalikan aliran item.
- Pemrosesan gambar dan video: Memproses file media besar dalam potongan-potongan.
- Analisis log: Menganalisis file log besar baris per baris.
Contoh - Data Saham Internasional: Pertimbangkan aplikasi yang mengambil kutipan saham real-time dari berbagai bursa internasional. Iterator asinkron dapat digunakan untuk mengalirkan data, dan 'for each' asinkron dapat memproses setiap kutipan, memperbarui antarmuka pengguna dengan harga terbaru. Ini dapat digunakan untuk menampilkan harga saham terkini dari perusahaan seperti:
- Tencent (Cina): Mengambil data saham dari perusahaan teknologi internasional besar
- Tata Consultancy Services (India): Menampilkan pembaruan saham dari perusahaan layanan TI terkemuka
- Samsung Electronics (Korea Selatan): Menampilkan harga saham dari produsen elektronik global
- Toyota Motor Corporation (Jepang): Memantau harga saham dari produsen mobil internasional
Kesimpulan
Iterator asinkron dan pembantu 'for each' asinkron menyediakan cara yang kuat dan elegan untuk memproses aliran data secara asinkron di JavaScript. Dengan merangkum logika iterasi, Anda dapat menyederhanakan kode Anda, meningkatkan keterbacaan, dan meningkatkan kinerja aplikasi Anda. Dengan menangani kesalahan, mengontrol konkurensi, dan memungkinkan pembatalan, Anda dapat membuat pipeline pemrosesan data asinkron yang kuat dan dapat diskalakan.